/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License specifically applicable to
 * this software at http://developer.yahoo.com/yslow/license.txt, or the general
 * form at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is: YSlow
 *
 * The Initial Developer of the Original Code is Yahoo! Inc.
 *
 * Copyright (C) 2007, Yahoo! Inc. All Rights Reserved.
 *
 * Contributor(s):
 * ***** END LICENSE BLOCK ***** */

//Create the Lints namespace within the global YSLOW namespace.
if ( typeof YSLOW.Lint == "undefined") {
	YSLOW.Lint = {};
}

// Common code for returning the security warning from a lint function.
YSLOW.Lint.lintSecurityWarning = function(hLint) {
	hLint["score"] = "unk";
	hLint["nonwarnings"] = "This test requires <a target=_blank class=eplink href='http://performance.corp.yahoo.com/yslow#permissions'>enabling Ajax permissions</a>";
	return hLint;
}


YSLOW.Lint.genSecurityWarning = function() {
    //dprint(9, "genSecurityWarning: enter function");
    return "<font class=warning>WARNING: For a full analysis you need to loosen Firefox's security settings. Go to <code>about:config</code> and toggle <code>signed.applets.codebase_principal_support</code> to <code>true</code>.</font>\n<p>\n";
}


// Warn if large numbers of JS or CSS components. Or images!
YSLOW.Lint.lintNumComps = function() {
    //dprint(9, "nSLOW.Lint.lintNumComps: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Make fewer HTTP requests";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#num_http";

	// Scripts
	var numComp = FirebugContext.yslowContext.count["js"];
	hLint["warnings"] = "";
	if ( numComp > YSLOW.getPref("yslow.maxJs", 3) ) {
		iScore -= (numComp - YSLOW.getPref("yslow.maxJs", 3)) * YSLOW.getPref("yslow.pointsJs", 4);
		hLint["warnings"] += "<p style=\"margin-top: 0px; color: #D23620;\">This page has " + numComp + " external JavaScript files.";
	}

	// Stylesheets
 	numComp = FirebugContext.yslowContext.count["css"];
	if ( numComp > YSLOW.getPref("yslow.maxCss", 2) ) {
		iScore -= (numComp - YSLOW.getPref("yslow.maxCss", 2)) * YSLOW.getPref("yslow.pointsCss", 4);
		hLint["warnings"] += "<p style=\"margin-top: 0px; color: #D23620;\">This page has " + numComp + " external StyleSheets.";
	}

	// Images
 	numComp = FirebugContext.yslowContext.count["cssimage"];
	if ( numComp > YSLOW.getPref("yslow.maxCssImage", 6) ) {
		iScore -= (numComp - YSLOW.getPref("yslow.maxCssImage", 6)) * YSLOW.getPref("yslow.pointsCssImage", 3);
		hLint["warnings"] += "<p style=\"margin-top: 0px; color: #D23620;\">This page has " + numComp + " CSS background images.";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["NumComps"] = iScore;

	return hLint;
}

// Check if all the components are from a CDN hostname.
YSLOW.Lint.lintCDN = function() {
    //dprint(9, "lintCDN: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Use a CDN";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#cdn";

	var sNotCDN = "", sExceptions = "";
	var aComponents = FirebugContext.yslowContext.getComponents();
	var headersDivId = "";
	for ( var i = 0; i < aComponents.length; i++ ) {
		var compObj = aComponents[i];
		var sType = YSLOW.Component.getCompVal(compObj, "type");
		if ( "image" == sType || "cssimage" == sType || "js" == sType || "css" == sType || "flash" == sType ) {
			var url = YSLOW.Component.getCompVal(compObj, "url");
			var iNotCDN = YSLOW.Utils.isNotCDN(url);

			//Lets add information for headers as well.
			//This is the ID for the DIV for displaying the headers.
			headersDivId = "headerCdnDiv_" + i;

			if ( 1 == iNotCDN ) {
                sNotCDN += "<li class=nobullet>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + YSLOW.Utils.prettyAnchor(url, url, "perfWarning", true, undefined, undefined, sType);
				sNotCDN += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);
				iScore -= YSLOW.getPref("yslow.pointsNotCDN", 10);
			}
			else if ( -1 == iNotCDN ) {
                sExceptions += "<li class=nobullet>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + YSLOW.Utils.prettyAnchor(url, url, "nonwarning", undefined, undefined, undefined, sType);
				sExceptions += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);
			}
		}
	}

	var CDNs = YSLOW.getPref("yslow.cdnHostnames", "");
	if ( CDNs ) {
		// Remind the user we're using the CDNs from their prefs.
		hLint["warnings"] = '<font color="#660066"><i><b>Using these CDN hostnames from your <a href="javascript:document.yslowPanel.openLink(\'http://developer.yahoo.com/yslow/faq.html#faq_cdn\')">preferences</a>: ' + CDNs + "</b></i></font>" +
				( sNotCDN.length || sExceptions.length ? "<br>" : "" );
	}
	else if ( sNotCDN.length || sExceptions.length ) {
		// No CDNs in their prefs - remind the user they can
		hLint["warnings"] = '<font color="#660066"><i><b>You can add your own CDN hostname <a href="javascript:document.yslowPanel.openLink(\'http://developer.yahoo.com/yslow/faq.html#faq_cdn\')">preferences</a>.</b></i></font><br>';
	}

	if ( sNotCDN.length ) {
		hLint["warnings"] += "These components are not on a CDN:<ul class=smalllist>" + sNotCDN + "</ul>";
	}
	if ( sExceptions.length ) {
		hLint["nonwarnings"] += "These components are not on a CDN, but are exceptions:<ul class=smalllist>" + sExceptions + "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["CDN"] = iScore;

	return hLint;
}


// Check that (almost) all components have a far-future Expires header.
YSLOW.Lint.lintExpires = function() {
    //dprint(9, "lintExpires: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Add an Expires header";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#expires";

	if ( FirebugContext.yslowContext["bPermDisabled"] ) {
		// This test is not possible if Ajax permissions are disabled.
		return YSLOW.Lint.lintSecurityWarning(hLint);
	}

	// We only mention JS and CSS, but I've seen images with no Expires. Check everything.
	var aTypes = ["css", "js", "image", "cssimage", "flash"];
	var sWarnings = "";
	var sSummary = "<br><br>SUMMARY: with FFE:<br>";  // details about Expires and max-age
	var sLastMods = "";
	var aLastMods = [];
	var iMaxAge = 0;
	var headersDivId = "";
	var headersCount = 0;

	for ( var iType=0; iType < aTypes.length; iType++ ) {
		var sType = aTypes[iType];
		var ffecntr = 0;
		var aComponents = FirebugContext.yslowContext.getComponents(sType);

		for ( var i = 0; i < aComponents.length; i++ ) {
			var compObj = aComponents[i];
			var url = YSLOW.Component.getCompVal(compObj, "url");

			if ( -1 != YSLOW.Component.getMaxAge(compObj) ) {
				iMaxAge++;   // count how many have a max-age header
			}

			if ( ! YSLOW.Component.hasFarFutureExpiresOrMaxAge(compObj) ) {     // does NOT have a far future Expires
				var expires = YSLOW.Component.getCompVal(compObj, "expires");
				prettyExpires = YSLOW.Utils.prettyExpiresDate(expires);
				if ( "" == prettyExpires ) {
					prettyExpires = "no expires";
				}

				//This is the ID for the DIV for displaying the headers.
				headersDivId = "headerExpiresDiv_" + headersCount++;

                sWarnings += "<li><nobr>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + "(" + prettyExpires + ") " + YSLOW.Utils.prettyAnchor(url, url, "perfWarning", undefined, undefined, undefined, compObj.type) + "</nobr>";
				sWarnings += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);		//Display the header info here.
				iScore -= YSLOW.getPref("yslow.pointsExpiresHeader", 11);
				ffecntr++;
				var modified_date = YSLOW.Component.getCompVal(compObj, "last-modified");
				if ( modified_date ) {
					aLastMods[aLastMods.length] = ( Number(new Date()) - YSLOW.Utils.epochMS(modified_date) );
					sLastMods += modified_date + "<br>";
				}
			}
		}
		if ( 0 < aComponents.length ) {
			sSummary += sType + ": " + (aComponents.length-ffecntr) + "/" + aComponents.length + "<br>";
		}
	}

	var bShowFFEDetails = YSLOW.getPref("yslow.showExpiresDetails", false); 	// by default do NOT show the details
	if ( bShowFFEDetails ) {
		sWarnings += "<li><nobr>"+ sSummary;
		if ( aLastMods.length ) {
			aLastMods.sort(YSLOW.Utils.compareNumbers);
			var median = aLastMods[parseInt(aLastMods.length/2)];
			sWarnings += "<li><nobr>"+ aLastMods.length + " last modified dates, median = " + median + " (" + parseInt(median/(1000*60*60*24)) + " days)<br>";
		}
		sWarnings += "<li><nobr>Number of components with max-age: " + iMaxAge + "<br>";
		sWarnings += "<li><nobr>"+sLastMods;
	}

	if ( 0 < sWarnings.length ) {
		hLint["warnings"] = "These components do not have a far future Expires header:<ul class=smalllist>" + sWarnings + "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["Expires"] = iScore;

	return hLint;
}



// Check that (almost) all components are gzipped.
YSLOW.Lint.lintGzip = function() {
    //dprint(9, "lintGzip: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Gzip components";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#gzip";

	if ( FirebugContext.yslowContext["bPermDisabled"] ) {
		// This test is not possible if Ajax permissions are disabled.
		return YSLOW.Lint.lintSecurityWarning(hLint);
	}

	// Don't do images and flash. DO do html (TODO).
	var aTypes = ["doc", "iframe", "xhr", "js", "css"];
	var sWarnings = "";
	var headersDivId = "";
	var headersCount = 0;

	for ( var iType=0; iType < aTypes.length; iType++ ) {
		var sType = aTypes[iType];
		var aComponents = FirebugContext.yslowContext.getComponents(sType);
		for ( var i = 0; i < aComponents.length; i++ ) {
			var compObj = aComponents[i];
			var url = YSLOW.Component.getCompVal(compObj, "url");
			var gzip = YSLOW.Component.getCompVal(compObj, "compressed");
			var size = ( "" == YSLOW.Component.getCompVal(compObj, "content-length") ? 0 : parseInt(YSLOW.Component.getCompVal(compObj, "content-length")) );

			if ( "gzip" != gzip && "deflate" != gzip && size > YSLOW.getPref("yslow.gzipFileSize", 500) ) {

				//Lets add information for headers as well.
				//This is the ID for the DIV for displaying the headers.
				headersDivId = "headerGzipDiv_" + headersCount++;

				sWarnings += "<li><nobr>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + "(" + YSLOW.Utils.kbSize(YSLOW.Component.getCompVal(compObj, "content-length")) + ") " + YSLOW.Utils.prettyAnchor(url, url, "perfWarning") + "</nobr>";
				sWarnings += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);		//Display the header info here.
				iScore -= YSLOW.getPref("yslow.pointsGzip", 11);
			}
		}
	}

	if ( 0 < sWarnings.length ) {
		hLint["warnings"] = "These components are not gzipped:<ul class=smalllist>" + sWarnings + "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["Gzip"] = iScore;

	return hLint;
}


// Check that CSS is always at the top of the page.
// This is inexact. There is no way in FF to see exactly where the component is.
// In FF the stylesheets are made children of the HEAD element, so we can't use the DOM
// to detect where they occurred in the original doc (and thus when they were downloaded).
// So instead we actually parse the HTML document looking for the CSS filename.
// This could be inexact in many (edge case?) situations:
//	  - if the CSS filename occurs as a comment in the doc
//    - the CSS file was itself imported by another CSS file (and thus is not mentiond in the doc)
//    - the CSS file was loaded using JavaScript and the filename doesn't appear verbatim
YSLOW.Lint.lintCssAtTop = function() {
    //dprint(9, "lintCssAtTop: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Put CSS at the top";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#css_top";
	var headersCount = 0;
	var headersDivId = "";

	var aComponents = FirebugContext.yslowContext.getComponents("doc");
	var aUrls = [];
	if ( aComponents[0] && aComponents[0]['responseText'] ) {
		var sHtml = aComponents[0].responseText.toLowerCase();  // assume the first "doc" is the original HTML doc
		var i = sHtml.indexOf("<body");
		if ( -1 != i ) {
			sHtml = aComponents[0].responseText.substring(0, i);  // grab the HTML that is NOT lower cased, so the css filenames match
			var aComponents = FirebugContext.yslowContext.getComponents("css");
			for ( var i = 0; i < aComponents.length; i++ ) {
				var compObj = aComponents[i];
				if ( ! YSLOW.Utils.parseFindUrlFilename(compObj['url'], sHtml) ) {
					aUrls[aUrls.length] = compObj['url'];
					iScore -= ( 100 == iScore ? 1 : 0 );  // start at 99 so each ding drops us a grade
					iScore -= YSLOW.getPref("yslow.pointsCssAtTop", 10);
				}
			}
		}
	}

	if ( 0 < aUrls.length ) {
		hLint["warnings"] = aUrls.length + " external stylesheets were found outside the document HEAD.<ul class=smalllist>";
		for ( var i = 0; i < aUrls.length; i++ ) {

			//Lets add information for headers as well.
			//This is the ID for the DIV for displaying the headers.
			headersDivId = "headerCssAtTopDiv_" + headersCount++;

			hLint["warnings"] += "<li>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + YSLOW.Utils.prettyAnchor(aUrls[i], aUrls[i], "perfWarning");
			hLint["warnings"] += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);		//Display the header info here.
		}
		hLint["warnings"] += "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["CssAtTop"] = iScore;

	return hLint;
}


// Check if JS is low in the page.
YSLOW.Lint.lintJsAtBottom = function() {
    //dprint(9, "lintJsAtBottom: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Put JS at the bottom";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#js_bottom";
	var headersCount = 0;
	var headersDivId = "";

	var aComponents = FirebugContext.yslowContext.getComponents("doc");
	var aUrls = [];
	if ( aComponents[0] && aComponents[0]['responseText'] ) {
		var sHtml = aComponents[0].responseText.toLowerCase();  // assume the first "doc" is the original HTML doc
		var i = sHtml.indexOf("<body");
		if ( -1 != i ) {
			sHtml = aComponents[0].responseText.substring(0, i);  // grab the HTML that is NOT lower cased, so the css filenames match
			var aComponents = FirebugContext.yslowContext.getComponents("js");
			for ( var i = 0; i < aComponents.length; i++ ) {
				var compObj = aComponents[i];
				if ( YSLOW.Utils.parseFindUrlFilename(compObj['url'], sHtml) ) {
					aUrls[aUrls.length] = compObj['url'];
					iScore -= ( 100 == iScore ? 1 : 0 );  // start at 99 so every two dings drops us a grade
					iScore -= YSLOW.getPref("yslow.pointsJsAtBottom", 5);
				}
			}
		}
	}

	if ( 1 < aUrls.length ) {
		hLint["warnings"] = aUrls.length + " external scripts were found in the document HEAD. Could they be moved lower in the page?<ul class=smalllist>";
		for ( var i = 0; i < aUrls.length; i++ ) {

			//Lets add information for headers as well.
			//This is the ID for the DIV for displaying the headers.
			headersDivId = "headerJsAtBottomDiv_" + headersCount++;

			hLint["warnings"] += "<li>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + YSLOW.Utils.prettyAnchor(aUrls[i], aUrls[i], "perfWarning");
			hLint["warnings"] += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);		//Display the header info here.
		}
		hLint["warnings"] += "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["JsAtBottom"] = iScore;

	return hLint;
}




// Check for expressions in CSS.
YSLOW.Lint.lintExpression = function() {
    //dprint(9, "lintExpression: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Avoid CSS expressions";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#css_expressions";
	var headersCount = 0;
	var headersDivId = "";

	var numExpr = 0;
	var aUrls = [];

	// Iterate over external stylesheets.
	var aObjects = [];
	var aComponents = FirebugContext.yslowContext.getComponents("css");
	for ( var i = 0; i < aComponents.length; i++ ) {
		var compObj = aComponents[i];
		if ( 0 < compObj.expressionCount() ) {
			numExpr += compObj.expressionCount();
			aUrls[aUrls.length] = compObj['url'] + " (" + compObj.expressionCount() + ")";
		}
	}

	// Now iterate over internal stylesheets.
	var aElements = FirebugContext.yslowContext["document"].getElementsByTagName("style");
	for (var i = 0; i < aElements.length; i++) {
		var styleSheet = aElements[i];
		if ( styleSheet.innerHTML ) {
			var n = YSLOW.Component.parseExpressions(styleSheet.innerHTML);
			if ( 0 < n ) {
				numExpr += n;
				aUrls[aUrls.length] = "inline style block" + ( styleSheet.id ? ", id = " + styleSheet.id : "" ) + " (" + n + ")";
			}
		}
	}

	if ( 0 < numExpr ) {
		iScore = 90 - (numExpr * YSLOW.getPref("yslow.pointsCssExpressions", 2) );
		hLint["warnings"] =
			numExpr + ( 1 < numExpr ? " rules" : " rule" ) + " containing \"expression\" " +
			( 1 < numExpr ? " were" : " was" ) + " found across all the CSS text." +
			"<ul class=smalllist>";
		for ( var i = 0; i < aUrls.length; i++ ) {

			//Lets add information for headers as well.
			//This is the ID for the DIV for displaying the headers.
			headersDivId = "headerExpressionDiv_" + headersCount++;

			hLint["warnings"] += "<li>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + YSLOW.Utils.prettyAnchor(aUrls[i], aUrls[i], "perfWarning"); // TODO - the href is bogus if "inline style block"
			hLint["warnings"] += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);		//Display the header info here.
		}
		hLint["warnings"] += "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["Expression"] = iScore;

	return hLint;
}



// Check if home pages embed all JS and CSS.
YSLOW.Lint.lintExternalFiles = function() {
    //dprint(9, "lintExternal: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Make JS and CSS external";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#external";

	hLint["nonwarnings"] = "Only consider this if your property is a common user home page.";
	hLint["score"] = "n/a";
	// This rule requires manual evaluation. Don't score it automatically.
	// FirebugContext.yslowContext["lintscores"]["ExternalFiles"] = iScore;

	return hLint;
}


// Check how many domains are needed to render the page.
YSLOW.Lint.lintDomains = function() {
    //dprint(9, "lintDomains: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Reduce DNS lookups";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#dns_lookups";

	var aComponents = FirebugContext.yslowContext.getComponents();
	var hHostnames = {};
	hHostnames[FirebugContext.yslowContext["document"].location.hostname] = 1;
	for ( var i = 0; i < aComponents.length; i++ ) {
		var compObj = aComponents[i];
		var url = YSLOW.Component.getCompVal(compObj, "url");
		// All of the URLs are supposed to be full URLs.
		var iSlashSlash = url.indexOf("//");
		var iPath = url.indexOf("/", iSlashSlash+2);
		var hostname = url.substring(iSlashSlash+2, iPath);
		if ( hostname.match(/\.[A-Za-z]/) ) {  // do NOT penalize if an IP address is used for the hostname (eg, "http://111.222.333.444/...")
			// DNS lookups are case insensitive. Browsers convert to lowercase before doing the lookup.
			hHostnames[hostname.toLowerCase()] = 1;
		}
	}

	var numDomains = 0, sDomains = "";
	for ( var domain in hHostnames ) {
		numDomains++;
		sDomains += "<li>" + domain;
	}

	if ( YSLOW.getPref("yslow.numberOfDomains", 4) < numDomains ) {
		hLint["warnings"] = "This page has more than " + YSLOW.getPref("yslow.numberOfDomains", 4) + " domains.<ul class=smalllist>" + sDomains + "</ul>";
	}

	iScore -= (numDomains-2) * YSLOW.getPref("yslow.pointsDomains", 5);
	if ( iScore > 100 ) iScore = 100;

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["Domains"] = iScore;

	return hLint;
}


// Check if the JS code is obfuscated.
YSLOW.Lint.lintMinify = function() {
    //dprint(9, "lintMinify: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Minify JS";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#minify";
	var headersCount = 0;
	var headersDivId = "";

	if ( FirebugContext.yslowContext["bPermDisabled"] ) {
		// This test is not possible if Ajax permissions are disabled.
		return YSLOW.Lint.lintSecurityWarning(hLint);
	}

	// Iterate through all the JS components and check if they contain comments or whitespace.
	var sWarnings = "";
	var aComponents = FirebugContext.yslowContext.getComponents("js");
	for ( var i = 0; i < aComponents.length; i++ ) {
		var compObj = aComponents[i];
		var url = YSLOW.Component.getCompVal(compObj, "url");

		if ( ! compObj.isMinified() ) {
			//Lets add information for headers as well.
			//This is the ID for the DIV for displaying the headers.
			headersDivId = "headerObfuscateDiv_" + headersCount++;

			sWarnings += "<li><nobr>"+ YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) +"(" + YSLOW.Utils.kbSize(YSLOW.Component.getCompVal(compObj, "content-length")) + ") " + YSLOW.Utils.prettyAnchor(url, url, "perfWarning") + "</nobr>";
			sWarnings += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);		//Display the header info here.

			iScore -= YSLOW.getPref("yslow.pointsJsObfuscate", 10);
		}
	}

	if ( "" != sWarnings ) {
		hLint["warnings"] = "The following JavaScript files do not appear to be obfuscated nor minified.<ul class=smalllist>" + sWarnings + "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["Obfuscate"] = iScore;

	return hLint;
}


// Check for redirects.
YSLOW.Lint.lintRedirects = function() {
    //dprint(9, "lintRedirects: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Avoid redirects";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#redirects";
	var headersCount = 0;
	var headersDivId = "";

	if ( FirebugContext.yslowContext["bPermDisabled"] ) {
		// This test is not possible if Ajax permissions are disabled.
		return YSLOW.Lint.lintSecurityWarning(hLint);
	}

	// Iterate through all the redirects.
	var sWarnings = "";
	var aComponents = FirebugContext.yslowContext.getComponents("redirect");
	for ( var i = 0; i < aComponents.length; i++ ) {
		var compObj = aComponents[i];
		var url = YSLOW.Component.getCompVal(compObj, "url");
		var location = YSLOW.Component.getCompVal(compObj, "location");

		//Lets add information for headers as well.
		//This is the ID for the DIV for displaying the headers.
		headersDivId = "headerRedirectsDiv_" + headersCount++;

		sWarnings += "<li><nobr>"+ YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + YSLOW.Utils.prettyAnchor(url, url, "perfWarning") + " redirects to </nobr><nobr>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + YSLOW.Utils.prettyAnchor(location, location, "perfWarning") + "</nobr>";
		sWarnings += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);		//Display the header info here.

		iScore -= YSLOW.getPref("yslow.pointsRedirects", 10);
	}

	if ( "" != sWarnings ) {
		hLint["warnings"] = "The following redirects were found.<ul class=smalllist>" + sWarnings + "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["Redirects"] = iScore;

	return hLint;
}


// Check that no JS component is loaded multiple times.
YSLOW.Lint.lintJsTwice = function() {
    //dprint(9, "lintJsTwice: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Remove duplicate scripts";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#js_dupes";
	var headersCount = 0;
	var headersDivId = "";

	var aComponents = FirebugContext.yslowContext.getComponents("js");
	var hUrls = {}, nMultipleJs = 0;
	for ( var i = 0; i < aComponents.length; i++ ) {
		var compObj = aComponents[i];
		var url = YSLOW.Component.getCompVal(compObj, "url");
		if ( "undefined" != typeof(hUrls[url]) ) {
			hUrls[url] += 1;
			nMultipleJs++;
		}
		else
			hUrls[url] = 1;
	}

	if ( 0 != nMultipleJs ) {
		hLint["warnings"] = "This page has " + nMultipleJs + " redundant JS components:\n";
		for ( var key in hUrls ) {
			if ( hUrls[key] > 1 ) {
				//Lets add information for headers as well.
				//This is the ID for the DIV for displaying the headers.
				headersDivId = "headerJsTwiceDiv_" + headersCount++;

				hLint["warnings"] += "<p class=indent1>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + (hUrls[key]-1) + "&nbsp;" + YSLOW.Utils.prettyAnchor(key, key, "perfWarning") + "\n";
				hLint["warnings"] += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj) + "</li>\n";		//Display the header info here.
			}
		}
		iScore = 90 - ( nMultipleJs * YSLOW.getPref("yslow.pointsJsTwice", 5) );
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["JsTwice"] = iScore;

	return hLint;
}


// Check that (almost) all components do NOT have the ETag http response header.
YSLOW.Lint.lintEtags = function() {
    //dprint(9, "lintEtags: enter function");
	var iScore = 100;
	var hLint = {};
	hLint["ruletext"] = "Configure ETags";
	hLint["rulehref"] = "http://developer.yahoo.com/performance/rules.html#etags";
	var headersCount = 0;
	var headersDivId = "";

	if ( FirebugContext.yslowContext["bPermDisabled"] ) {
		// This test is not possible if Ajax permissions are disabled.
		return YSLOW.Lint.lintSecurityWarning(hLint);
	}

	// Don't do HTML since etags that break caching might be intentional (and acceptable).
	var aTypes = ["flash", "js", "css", "cssimage", "image"];
	var sWarnings = "";
	for ( var iType=0; iType < aTypes.length; iType++ ) {
		var sType = aTypes[iType];
		var aComponents = FirebugContext.yslowContext.getComponents(sType);
		for ( var i = 0; i < aComponents.length; i++ ) {
			var compObj = aComponents[i];
			if ( YSLOW.Component.hasBadEtag(compObj) ) {
				var url = YSLOW.Component.getCompVal(compObj, "url");

				//Lets add information for headers as well.
				//This is the ID for the DIV for displaying the headers.
				headersDivId = "headerEtagsDiv_" + headersCount++;

                sWarnings += "<li>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) + YSLOW.Utils.prettyAnchor(url, url, "perfWarning", undefined, undefined, undefined, compObj.type);
				sWarnings += "<li id='"+ headersDivId + "' style='display: none;'>" + YSLOW.Component.printComponentHeaders(compObj);		//Display the header info here.

				iScore -= YSLOW.getPref("yslow.pointsEtags", 11);
			}
		}
	}

	if ( 0 < sWarnings.length ) {
		hLint["warnings"] = "These components have an ETag:<ul class=smalllist>" + sWarnings + "</ul>";
	}

	hLint["score"] = iScore;
	FirebugContext.yslowContext["lintscores"]["ETags"] = iScore;

	return hLint;
}

